use proptest::{
    arbitrary::any,
    collection::{vec as arb_vec, SizeRange},
    prop_compose, prop_oneof,
    strategy::Just,
    strategy::Strategy,
};

use super::record::Record;

/// Input action for the model/system under test.
///
/// These actions map directly to methods that can be called from the reader or writer.
///
/// One notable difference compared to the real types is that acknowledgements here are only
/// allowed to acknowledge one read at a time.
#[derive(Clone, Debug)]
pub enum Action {
    WriteRecord(Record),
    FlushWrites,
    ReadRecord,
    AcknowledgeRead,
}

prop_compose! {
    fn arb_ack_amount(limit: usize)(amount in 1..limit) -> usize {
        amount
    }
}

fn arb_action() -> impl Strategy<Value = Action> {
    // Overall, we want reads and writes to be equal, with slightly fewer acks, and slightly fewer
    // still flushes of writes.
    prop_oneof![
        3 => Just(Action::FlushWrites),
        5 => Just(Action::ReadRecord),
        4 => Just(Action::AcknowledgeRead),
        5 => any::<(u32, u16, u8, u8)>().prop_map(|(id, base_size, size_offset, event_count)| {
            let size = u32::from(base_size) + u32::from(size_offset);
            let event_count = event_count % 7;
            Action::WriteRecord(Record::new(id, size, u32::from(event_count)))
        }),
    ]
}

pub fn arb_actions<R>(len_range: R) -> impl Strategy<Value = Vec<Action>>
where
    R: Into<SizeRange>,
{
    arb_vec(arb_action(), len_range).prop_map(sanitize_raw_actions)
}

/// Sanitizes raw actions generated by proptest into a valid sequence.
///
/// While we obviously want proptest to generate randomized orderings of actions to exercise our
/// model and our system under test (SUT), there are certain combinations/orderings that are simply
/// not valid.  For example, an acknowledgement is only generated by the user of a buffer once
/// they've gotten a record from a `read` operation, so allowing an acknowledgement to exist in an
/// action sequence where there is no read is not valid and never will be.  There is no reason to
/// test such a sequence.
pub fn sanitize_raw_actions(actions: Vec<Action>) -> Vec<Action> {
    let mut unread_event_count: usize = 0;
    let mut unacked_events: usize = 0;

    actions
        .into_iter()
        .filter_map(|a| match a {
            Action::WriteRecord(record) => {
                unread_event_count += 1;
                Some(Action::WriteRecord(record))
            }
            Action::ReadRecord => {
                if unread_event_count > 0 {
                    unread_event_count -= 1;
                    unacked_events += 1;
                }
                Some(Action::ReadRecord)
            }
            Action::AcknowledgeRead => (unacked_events > 0).then(|| {
                unacked_events -= 1;
                Action::AcknowledgeRead
            }),
            Action::FlushWrites => Some(Action::FlushWrites),
        })
        .collect::<Vec<_>>()
}
